package com.leyou.search.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.auth.entity.UserInfo;
import com.leyou.common.pojo.Favorite;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.pojo.utils.JsonUtils;
import com.leyou.item.pojo.*;
import com.leyou.search.Repository.GoodsRepository;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecClient;
import com.leyou.search.interceptor.LoginInterceptor;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;

@Service
public class SearchService {


    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private StringRedisTemplate template;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    private ObjectMapper mapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

    public SearchResult search(SearchRequest request) {
        String key = request.getKey();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(key)) {
            return null;
        }
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        QueryBuilder basicQuery = buildBasicQueryWithFilter(request);
        queryBuilder.withQuery(basicQuery);
        // 通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));

        // 分页
        searchWithPageAndSort(queryBuilder, request);

        // 聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId"));
        queryBuilder.addAggregation(AggregationBuilders.terms("category").field("cid3"));

        // 执行查询获取结果集
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        // 获取聚合结果集
        // 商品分类的聚合结果
        List<Category> categories =
                getCategoryAggResult(goodsPage.getAggregation("category"));
        // 品牌的聚合结果
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation("brands"));

        // 根据商品分类判断是否需要聚合
        List<Map<String, Object>> specs = new ArrayList<>();
        if (categories == null) {
            Integer t = goodsPage.getTotalPages();
            long l = t.longValue();
            return new SearchResult(goodsPage.getTotalElements(), l, goodsPage.getContent(), categories, brands, specs);
        }
        if (categories.size() == 1) {
            // 如果商品分类只有一个才进行聚合，并根据分类与基本查询条件聚合
            specs = getSpec(categories.get(0).getId(), basicQuery);
        }
        Integer t = goodsPage.getTotalPages();
        long l = t.longValue();
        return new SearchResult(goodsPage.getTotalElements(), l, goodsPage.getContent(), categories, brands, specs);
    }

    /**
     * 聚合出规格参数
     *
     * @param cid
     * @param query
     * @return
     */
    private List<Map<String, Object>> getSpec(Long cid, QueryBuilder query) {
        try {
            // 不管是全局参数还是sku参数，只要是搜索参数，都根据分类id查询出来
            List<SpecParam> params = this.specificationClient.querySpecParamByGid(null, cid, true, null);
            List<Map<String, Object>> specs = new ArrayList<>();

            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            queryBuilder.withQuery(query);

            // 聚合规格参数
            params.forEach(p -> {
                String key = p.getName();
                queryBuilder.addAggregation(AggregationBuilders.terms(key).field("specs." + key + ".keyword"));

            });

            // 查询
            Map<String, Aggregation> aggs = this.elasticsearchTemplate.query(queryBuilder.build(),
                    SearchResponse::getAggregations).asMap();

            // 解析聚合结果
            params.forEach(param -> {
                Map<String, Object> spec = new HashMap<>();
                String key = param.getName();
                spec.put("k", key);
                StringTerms terms = (StringTerms) aggs.get(key);
                spec.put("options", terms.getBuckets().stream().map(StringTerms.Bucket::getKeyAsString));
                specs.add(spec);
            });

            return specs;
        } catch (
                Exception e)

        {
            logger.error("规格聚合出现异常：", e);
            return null;
        }

    }

    // 构建基本查询条件
    private QueryBuilder buildBasicQueryWithFilter(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 基本查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 过滤条件构建器
        BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
        // 整理过滤条件
        Map<String, String> filter = request.getFilter();
        if (!CollectionUtils.isEmpty(filter)) {
            for (Map.Entry<String, String> entry : filter.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                // 商品分类和品牌要特殊处理
                if (!key.equals("分类") && !key.equals("品牌")) {
                    key = "specs." + key + ".keyword";
                }
                if (key.equals("分类")) {
                    key = "cid3";
                }
                if (key.equals("品牌")) {
                    key = "brandId";
                }
                // 字符串类型，进行term查询
                filterQueryBuilder.must(QueryBuilders.termQuery(key, value));
            }
        }

        // 添加过滤条件
        queryBuilder.filter(filterQueryBuilder);
        return queryBuilder;
    }

    // 构建基本查询条件
    private void searchWithPageAndSort(NativeSearchQueryBuilder queryBuilder, SearchRequest request) {
        // 准备分页参数
        int page = request.getPage();
        int size = request.getSize();

        // 1、分页
        queryBuilder.withPageable(PageRequest.of(page - 1, size));
        // 2、排序
        String sortBy = request.getSortBy();
        Boolean desc = request.getDescending();
        if (StringUtils.isNotBlank(sortBy)) {
            // 如果不为空，则进行排序
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));
        }
    }

    // 解析品牌聚合结果
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        try {
            LongTerms brandAgg = (LongTerms) aggregation;
            List<Long> bids = new ArrayList<>();
            for (LongTerms.Bucket bucket : brandAgg.getBuckets()) {
                bids.add(bucket.getKeyAsNumber().longValue());
            }
            // 根据id查询品牌
            return this.brandClient.queryBrandListByIds(bids);

        } catch (Exception e) {
            logger.error("品牌聚合出现异常：", e);
            return null;
        }
    }

    // 解析商品分类聚合结果
    private List<Category> getCategoryAggResult(Aggregation aggregation) {
        try {
            List<Category> categories = new ArrayList<>();
            LongTerms categoryAgg = (LongTerms) aggregation;
            List<Long> cids = new ArrayList<>();
            for (LongTerms.Bucket bucket : categoryAgg.getBuckets()) {
                cids.add(bucket.getKeyAsNumber().longValue());
            }
            // 根据id查询分类名称
            ResponseEntity<List<String>> listResponseEntity = this.categoryClient.queryNameByIds(cids);
            List<String> names = listResponseEntity.getBody();
            for (int i = 0; i < names.size(); i++) {
                Category c = new Category();
                c.setId(cids.get(i));
                c.setName(names.get(i));
                categories.add(c);
            }
            return categories;
        } catch (Exception e) {
            logger.error("分类聚合出现异常：", e);
            return null;
        }
    }

    public Goods buildGoods(Spu spu) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Goods goods = new Goods();
        // 查询商品分类
        ResponseEntity<List<String>> listResponseEntity = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        List<String> names = listResponseEntity.getBody();
        // 查询品牌
        Brand brand = this.brandClient.queryBrandByBid(spu.getBrandId());
        // 查询该spu下的skus
        ResponseEntity<List<Sku>> listResponseEntity1 = this.goodsClient.querySkuBySpuId(spu.getId());
        List<Sku> skus = listResponseEntity1.getBody();

        // 查询所有搜索的规格参数
        List<SpecParam> params = this.specificationClient.querySpecParamByGid(null, spu.getCid3(), null, null);

        // 查询spudetail
        ResponseEntity<SpuDetail> spuDetailResponseEntity = this.goodsClient.querySpuDetailById(spu.getId());
        SpuDetail spuDetail = spuDetailResponseEntity.getBody();

        // 构建价格的集合
        List<Long> prices = new ArrayList<>();
        // 构建skuMap集合
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        // 遍历skus
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]);
            map.put("price", sku.getPrice());
            skuMapList.add(map);
        });

        // 获取spuDetail中的通用规格参数值
        Map<String, Object> genericSpec = mapper.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        // 获取spuDetail中的特殊规格参数值
        Map<String, List<Object>> specialSpec = mapper.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, Object>>() {
        });
        // 搜索规格参数及参数值集合
        Map<String, Object> searchingMap = new HashMap<>();
        params.forEach(param -> {
            if (param.getGeneric()) {
                String value = genericSpec.get(param.getId().toString()).toString();
                if (param.getNumeric()) {
                    value = chooseSegment(value, param);
                }
                searchingMap.put(param.getName(), value);
            } else {
                List<Object> value = specialSpec.get(param.getId().toString());
                searchingMap.put(param.getName(), value);
            }
        });

        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setBrandId(spu.getBrandId());
        goods.setSubTitle(spu.getSubTitle());
        // 商品标题，分类，品牌
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " ") + " " + brand.getName());
        // sku中所有价格的集合，方便以价格进行搜索
        goods.setPrice(prices);
        // 结果集中一个spu中的所有sku
        goods.setSkus(mapper.writeValueAsString(skuMapList));
        // 搜索的规格参数
        goods.setSpecs(searchingMap);
        return goods;
    }

    public void createIndex(Long id) throws IOException {
        Spu spu = this.goodsClient.querySpuById(id);
        Goods goods = buildGoods(spu);
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long id) {
        this.goodsRepository.deleteById(id);
    }


    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 分页查询猜你喜欢
     *
     * @param searchRequest
     * @return
     */
    public PageResult<Goods> guessYouLike(SearchRequest searchRequest) {


        //获取登录用户信息
        UserInfo userInfo = LoginInterceptor.getUserInfo();

        //查询redis 中保存的浏览足迹
        String key = "leyou:track:" + userInfo.getId().toString();

        if (!this.template.hasKey(key)) {
            return null;
        }
        BoundHashOperations<String, Object, Object> hashOps = this.template.boundHashOps(key);

        Map<Object, Object> entries = hashOps.entries();

        if (CollectionUtils.isEmpty(entries)) {
            return null;
        }

        List<Favorite> favorites = new ArrayList<>();

        for (Object o : entries.values()) {
            Favorite favorite = JsonUtils.parse(o.toString(), Favorite.class);
            favorites.add(favorite);
        }

        //List<Favorite> favorites = values.stream().map(value -> JsonUtils.parse(value.toString(), Favorite.class)).collect(Collectors.toList());

        Collections.sort(favorites, (Favorite o1, Favorite o2) -> {

            int result = (int) (o2.getCreateTime().getTime() - o1.getCreateTime().getTime());
            return result;
        });

        //设置搜索词，最近一次足迹分类名称
        searchRequest.setKey(favorites.get(0).getCname());

        //设置每页显示大小
        searchRequest.setSize(4);

        return this.search(searchRequest);
    }

    /**
     * 分页猜喜欢
     * @param request
     * @return
     */
    public List<Goods> guessYouLove(SearchRequest request) {
        String key = request.getKey();
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(QueryBuilders.matchQuery("all",request.getKey()));
        // 2、通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus"}, null));
        queryBuilder.withPageable(PageRequest.of(request.getPage() - 1,request.getSize()));
        Page<Goods> pageGoods = goodsRepository.search(queryBuilder.build());
        /*PageResult<Goods> result = new PageResult<>();
        result.setTotalPage((long) pageGoods.getTotalPages());
        result.setTotal(pageGoods.getTotalElements());
        result.setItems(pageGoods.getContent());*/
        return pageGoods.getContent();
    }


}